package com.izhengyin.gateway.base;

import com.izhengyin.gateway.base.constant.Error;
import com.izhengyin.gateway.base.utils.HttpUtils;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.cloud.gateway.support.ServiceUnavailableException;
import org.springframework.cloud.gateway.support.TimeoutException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.server.*;
import org.springframework.web.server.ResponseStatusException;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Map;

/**
 * @author zhengyin  zhengyinit@outlook.com
 * @date Created on 2019-05-20 12:16
 */
@Slf4j
public class ErrorHandler extends DefaultErrorWebExceptionHandler {
    private final MetricsService metricsService;
    public ErrorHandler(MetricsService metricsService , ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
                        ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
        this.metricsService = metricsService;
    }

    /**
     * 获取异常属性
     */
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Error error = Error.RUNTIME_ERROR;
        Throwable throwable = super.getError(request);
        String exception = throwable.getClass().getName();
        String errorMessage = this.buildErrorMessage(request, throwable);
        if(exception.equals(ResponseStatusException.class.getName())){
            if("404 NOT_FOUND".equals(throwable.getMessage())){
                error = Error.RESOURCE_NOT_FOUND;
            }
        }else if(exception.equals(HystrixRuntimeException.class.getName())){
            error = Error.SERVICE_UNAVAILABLE;
        }else if(exception.equals(TimeoutException.class.getName())){
            error = Error.GATEWAY_TIMEOUT;
        }else if(exception.equals(ServiceUnavailableException.class.getName())){
            error = Error.SERVICE_UNAVAILABLE;
        }else if(exception.equals(NotFoundException.class.getName())){
            error = Error.SERVICE_NOT_FOUND;
        }
        metricsService.createMetricsAndRecord(request.exchange(),throwable);
        if(Error.RESOURCE_NOT_FOUND.equals(error)){
            log.warn("{} , {} , {} , {} , {} ",
                    exception,
                    HttpUtils.getClientIp(request.exchange().getRequest()),
                    error.getError(),
                    errorMessage,
                    request.attribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR).map(Object::toString).orElse("none")
            );
        }else{
            log.error("{} , {} , {} , {} , {} ",
                    exception,
                    HttpUtils.getClientIp(request.exchange().getRequest()),
                    error.getError(),
                    errorMessage,
                    request.attribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR).map(Object::toString).orElse("none")
            );
        }
        return response(error);
    }

    /**
     * 指定响应处理方法为JSON处理的方法
     * @param errorAttributes
     */
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions
                //ajax 返回json
                .route(RequestPredicates.headers(headers -> HttpUtils.isXhrRequest(headers.asHttpHeaders())), this::renderErrorResponse)
                //正常 get 请求，返回页面
                .andRoute(RequestPredicates.all(),this::renderErrorView);
    }

    /**
     * 根据code获取对应的HttpStatus
     * @param errorAttributes 错误属性
     * @return http状态码
     */
    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        return getHttpStatus((int)errorAttributes.get("errno")).value();
    }

    /**
     * 构建异常信息
     * @param request
     * @param ex
     * @return 异常消息
     */
    private String buildErrorMessage(ServerRequest request, Throwable ex) {
        StringBuilder message = new StringBuilder("Failed to handle request [");
        message.append(request.methodName());
        message.append(" ");
        message.append(request.uri());
        message.append("]");
        if (ex != null) {
            message.append(": ");
            message.append(ex.getMessage());
        }
        return message.toString();
    }

    /**
     * 构建返回的JSON数据格式
     * @param errno
     * @return 响应参数
     */
    private static Map<String, Object> response(Error errno) {
        Map<String, Object> map = new HashMap<>();
        map.put("status", 0);
        map.put("errno",errno.getErrno());
        map.put("message", errno.getError());
        return map;
    }

    @Override
    protected Mono<ServerResponse> renderDefaultErrorView(ServerResponse.BodyBuilder responseBody, Map<String, Object> error) {
        StringBuilder builder = new StringBuilder();
        Object message = error.get("message");
        builder.append("<html>")
                .append("<body>")
                .append("<h1>抱歉，请求出错了，请稍后再试！</h1>")
                .append("<p>").append(message).append("</p>")
                .append("</body>")
                .append("</html>");
        return responseBody.bodyValue(builder.toString());
    }



    /**
     * 错误码，转换为HTTP状态码
     * @param errno 错误码
     * @return {@link HttpStatus}
     */
    private static HttpStatus getHttpStatus(int errno){
        if(errno == Error.RESOURCE_NOT_FOUND.getErrno()){
            return HttpStatus.valueOf(404);
        }
        if(errno == Error.RUNTIME_ERROR.getErrno()){
            return HttpStatus.valueOf(400);
        }
        if(errno == Error.GATEWAY_ERROR.getErrno()){
            return HttpStatus.valueOf(502);
        }
        if(errno == Error.GATEWAY_TIMEOUT.getErrno()){
            return HttpStatus.valueOf(504);
        }
        if(errno == Error.SERVICE_UNAVAILABLE.getErrno()){
            return HttpStatus.valueOf(503);
        }
        return HttpStatus.valueOf(502);
    }


}
